package com.sbtr.business.trainingtask.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sbtr.business.auth.service.impl.RoleServiceImpl;
import com.sbtr.business.classify.entity.AlgorithmInfo;
import com.sbtr.business.classify.mapper.AlgorithmMapper;
import com.sbtr.business.project.entity.Project;
import com.sbtr.business.project.service.impl.ProjectServiceImpl;
import com.sbtr.business.trainingserver.balancer.ITraningServerBalancer;
import com.sbtr.business.trainingserver.entity.TrainingServer;
import com.sbtr.business.trainingserver.service.ITrainingServerHttpApi;
import com.sbtr.business.trainingserver.service.impl.TrainingServerServiceImpl;
import com.sbtr.business.trainingtask.entity.TrainingAssign;
import com.sbtr.business.trainingtask.entity.TrainingInfo;
import com.sbtr.business.trainingtask.entity.TrainingStatusDto;
import com.sbtr.business.trainingtask.mapper.TrainingInfoMapper;
import com.sbtr.business.trainingtask.service.ITrainingInfoService;
import com.sbtr.common.PageDto;
import com.sbtr.constant.ProjectConstant;
import com.sbtr.exception.BDException;
import com.sbtr.open.project.dto.ProjectDto;
import com.sbtr.open.trainingmodel.dto.TrainingModelDataDto;
import com.sbtr.open.trainingserver.dto.TrainingServerDto;
import com.sbtr.open.trainingtask.dto.TrainingInfoDto;
import com.sbtr.util.BeanCopyUtils;
import com.sbtr.util.PageDtoUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.io.IOException;
import java.util.*;

/**
 * <p>
 * 训练任务表 服务实现类
 * </p>
 *
 * @author wangwh15
 */
@Transactional
@Service
public class TrainingInfoServiceImpl extends ServiceImpl<TrainingInfoMapper, TrainingInfo>
        implements ITrainingInfoService{

    private Logger logger = LoggerFactory.getLogger(TrainingInfoServiceImpl.class);

    @Autowired
    private RoleServiceImpl roleService;
    @Autowired
    private ITraningServerBalancer serverBalancer;
    @Autowired
    private ITrainingServerHttpApi serverHttpApi;
    @Autowired
    private TrainingAssignServiceImpl trainingAssignService;
    @Autowired
    private TrainingServerServiceImpl serverService;

    @Autowired
    private ProjectServiceImpl projectService;
    @Autowired
    AlgorithmMapper algorithmMapper;

    @Autowired
    TrainingInfoMapper trainingInfoMapper;
    /**
     * 标定文件上传路径
     */
    @Value("${training-task.txt-base-dir}")
    private String baseDir;

    @Override
     public TrainingInfoDto findById(Long id) {
        // 2019-11-04 modify by qiujun 内容：详情返回的接口增加任务剩余时间
        return BeanCopyUtils.copyProperties(getById(id), TrainingInfoDto.class);
    }

    @Override
     public boolean deleteById(Long id) {

        TrainingInfo TrainingInfo = baseMapper.selectById(id);
        // 只有创建者或者管理员有权限删除素材集
        Assert.isTrue((TrainingInfo != null)
//				&& TrainingInfo.getCreater().equalsIgnoreCase(UserUtils.getFdLoginName()))
//				|| roleService.isAdmin(UserUtils.getFdLoginName())
                ,
                "权限不足");
        // 状态为未开始或者错误的才可以删除
        String status = TrainingInfo.getStatus();
        if (TrainingInfoDto.TASK_STATUS_QUEUING.equals(status) || TrainingInfoDto.TASK_STATUS_RUNNING.equals(status)
                || TrainingInfoDto.TASK_STATUS_COMPLETE.equals(status)) {
            logger.warn("id为[{}]的训练任务状态为[{}],不能删除", id, status);
            throw new RuntimeException("训练任务状态为[" + status + "],不能删除");
        }
        // 删除与训练服务器的关联关系
        QueryWrapper<TrainingAssign> wrapper = new QueryWrapper<>();
        wrapper.eq("task_id", id);
        trainingAssignService.remove(wrapper);
        // 删除训练任务
        return removeById(id);
    }


    @Override
     public TrainingInfoDto getByProjectId(Long projectId) {

        QueryWrapper<TrainingInfo> wrapper = new QueryWrapper<>();
        if (projectId != null) {
            wrapper.eq("project_id", projectId);
        }

        List<TrainingInfo> list = this.list(wrapper);
        if (list != null && !list.isEmpty()) {
            if (list.size() != 1) {
                logger.warn("当前项目有多个上线的模型");
            }
            return BeanCopyUtils.copyProperties(list.get(0), TrainingInfoDto.class);
        }
        return null;
    }

    @Override
    public List<Map<String,Object>> getTrainingStatusList(String ids) {

        List<TrainingStatusDto> trainingInfoDto=new ArrayList<>();
        if(ids!=null){
            trainingInfoDto = trainingInfoMapper.getTrainingStatusLists(ids);
        }
        Map<String,String> params = new HashMap<>();
        trainingInfoDto.forEach(r->{
            String o =params.get(r.getProjectId());
            if(o==null&&!r.getStatus().equals("error")){
                params.put(r.getProjectId(),r.getStatus());
            }else{
                if("complete".equals(r.getStatus())){
                    params.put(r.getProjectId(),r.getStatus());
                }else if("queuing".equals(r.getStatus())&&!o.equals("complete")){
                    params.put(r.getProjectId(),r.getStatus());
                }else if(!"error".equals(r.getStatus())){
                    params.put(r.getProjectId(),r.getStatus());
                }
            }
        });
        List<Map<String,Object>> r = new ArrayList<>();

        int complete=0;
        int queuing=0;
        int NotStarted=0;
        if(params.size()!=0){
            for (Map.Entry<String,String> entry:params.entrySet()) {
                if("complete".equals(entry.getValue())){
                    complete++;
                }else if("queuing".equals(entry.getValue())){
                    queuing++;
                }else if("NotStarted".equals(entry.getValue())){
                    NotStarted++;
                }
            }
        }
        Map<String,Object> statusMap = new HashMap<>();
        statusMap.put("task1","未开始");
        statusMap.put("num",NotStarted);

        Map<String,Object> statusMap2 = new HashMap<>();
        statusMap2.put("task2","运行中");
        statusMap2.put("num",queuing);

        Map<String,Object> statusMap3 = new HashMap<>();
        statusMap3.put("task3","已结束");
        statusMap3.put("num",complete);

        r.add(statusMap);
        r.add(statusMap2);
        r.add(statusMap3);
        return r;
    }

    @Override
     public PageDto<TrainingInfoDto> search(String taskName,Long currPage, Long pageSize,Long projectId) {

        IPage<TrainingInfo> page = new Page<>();
        if (currPage != null) {
            page.setCurrent(currPage);
        }
        if (pageSize != null) {
            page.setSize(pageSize);
        }

        QueryWrapper<TrainingInfo> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotEmpty(taskName)) {
            wrapper.like("task_name", taskName);
        }
        if (projectId != null) {
            wrapper.eq("project_id", projectId);
        }

        ProjectDto projectDto = projectService.findById(projectId);
        if (projectDto.getCreater() != null) {
            wrapper.eq("creater", projectDto.getCreater());
        }

        wrapper.orderByDesc("create_time");

        page = page(page, wrapper);

        PageDto<TrainingInfoDto> pageDto = PageDtoUtils.getPageDtoByIPage(page, TrainingInfoDto.class);
//		this.setProjectName(pageDto.getList());
        return pageDto;
    }


    // ===================新版本新增接口====================//
    @Override
     public TrainingInfoDto create(TrainingInfoDto dto) throws IOException {
        //带bom转换成无bom
        String variableName = dto.getVariableName();
        if (StringUtils.isNotEmpty(variableName) && variableName.substring(0, 1).contains("\uFEFF")) {
            dto.setVariableName(variableName.substring(1));
        }
        String targetColumn = dto.getTargetColumn();
        if (StringUtils.isNotEmpty(targetColumn) && targetColumn.substring(0, 1).contains("\uFEFF")) {
            dto.setTargetColumn(targetColumn.substring(1));
        }

        TrainingInfo TrainingInfo = BeanCopyUtils.copyProperties(dto, TrainingInfo.class);

        if (dto.getId() == null) {
            // 新增
            // 查询项目进度(只有素材标注状态才会新建训练任务)
            ProjectDto projectDto = projectService.findById(dto.getProjectId());
            this.save(TrainingInfo);
            // 修改项目进度
            projectDto.setProgressRate(ProjectConstant.PROJECT_PROGRESS_TRAIN); // 模型训练
            projectService.updateById(BeanCopyUtils.copyProperties(projectDto, Project.class));
        } else {
            // 更新
            updateById(TrainingInfo);
        }
        dto.setId(TrainingInfo.getId());
        return dto;
    }

    @Override
    public List<Long> createBath(TrainingInfoDto dto)  {
        QueryWrapper<AlgorithmInfo> queryWrapper =new QueryWrapper<>();
        queryWrapper.eq("algorithm_type",dto.getType());
        List<AlgorithmInfo> list = algorithmMapper.selectList(queryWrapper);

        List<Long> ids = new ArrayList<>();
        List<TrainingInfo> saveList = new ArrayList<>();
        List<TrainingInfo> updateList = new ArrayList<>();
        for (AlgorithmInfo algorithm:list) {
            TrainingInfoDto dto2=BeanCopyUtils.copyProperties(dto,TrainingInfoDto.class);
            dto2.setCreateTime(new Date());
            dto2.setStatus(TrainingInfoDto.TASK_STATUS_NOTSTARTED);
            dto2.setAlgorithmName(algorithm.getAlgorithmName());
            dto2.setExtraParam(algorithm.getParameter());
            //带bom转换成无bom
            String variableName = dto2.getVariableName();
            if (StringUtils.isNotEmpty(variableName) && variableName.substring(0, 1).contains("\uFEFF")) {
                dto2.setVariableName(variableName.substring(1));
            }
            String targetColumn = dto2.getTargetColumn();
            if (StringUtils.isNotEmpty(targetColumn) && targetColumn.substring(0, 1).contains("\uFEFF")) {
                dto2.setTargetColumn(targetColumn.substring(1));
            }

            TrainingInfo trainingInfo = BeanCopyUtils.copyProperties(dto2, TrainingInfo.class);

            if(dto2.getId() == null){
                saveList.add(trainingInfo);
            }else{
                updateList.add(trainingInfo);
            }
        }

        if (CollectionUtil.isNotEmpty(saveList)) {
            this.saveBatch(saveList);
            for(TrainingInfo param:saveList) {
                ids.add(param.getId());
            }
            // 新增
            // 查询项目进度(只有素材标注状态才会新建训练任务)
            ProjectDto projectDto = projectService.findById(dto.getProjectId());
            // 修改项目进度
            projectDto.setProgressRate(ProjectConstant.PROJECT_PROGRESS_TRAIN); // 模型训练
            projectService.updateById(BeanCopyUtils.copyProperties(projectDto, Project.class));

        }
        if (CollectionUtil.isNotEmpty(updateList)) {
            this.updateBatchById(updateList);
            for(TrainingInfo param:updateList) {
                ids.add(param.getId());
            }
        }
        return ids;
    }


    public Map<Long, TrainingInfo> getNewestTaskByProject(List<Long> projectIds) {
        Map<Long, TrainingInfo> resultMap = new HashMap<>();
        QueryWrapper<TrainingInfo> wrapper = new QueryWrapper<>();
        wrapper.in("project_id", projectIds);
        wrapper.orderByDesc("create_time");
        List<TrainingInfo> list = this.list(wrapper);
        if (list != null) {
            for (TrainingInfo TrainingInfo : list) {
                if (resultMap.containsKey(TrainingInfo.getProjectId())) {
                    TrainingInfo task = resultMap.get(TrainingInfo.getProjectId());
                    if (TrainingInfo.getCreateTime().getTime() > task.getCreateTime().getTime()) {
                        resultMap.put(TrainingInfo.getProjectId(), TrainingInfo);
                    }
                } else {
                    resultMap.put(TrainingInfo.getProjectId(), TrainingInfo);
                }
            }
        }
        return resultMap;
    }

    // ===================新增启动数据预测任务====================//
    @Override
     public boolean startTask(Long id) {
        TrainingInfo task = getById(id);
        if (task != null) {
            // 判断该任务是否已经启动(已启动的不能再开始),一个训练任务只能启动一次
            String status = task.getStatus();
            if (TrainingInfoDto.TASK_STATUS_QUEUING.equals(status) || TrainingInfoDto.TASK_STATUS_RUNNING.equals(status)
                    || TrainingInfoDto.TASK_STATUS_STOP.equals(status)
                    || TrainingInfoDto.TASK_STATUS_COMPLETE.equals(status)) {
                logger.warn("id为[{}]的训练任务状态为[{}],不能重复启动", id, status);
                return false;
            }

            // 1、根据负载均衡器获取选择一台训练服务器
            TrainingServerDto serverDto = serverBalancer.chooseMachine();
            serverDto.setWaitTime(0);
            // 2、向此训练服务器发送训练任务，调用算法端 ip/addTask接口
            boolean tag = serverHttpApi.addTask(serverDto, BeanCopyUtils.copyProperties(task, TrainingInfoDto.class));
            // 3、修改任务状态为排队中状态
            if (tag) {
                task.setStatus(TrainingInfoDto.TASK_STATUS_QUEUING);//
                // 由python端去控制是否处于排队状态
                // 保存信息到(训练任务分配表)
                TrainingAssign trainingAssign = new TrainingAssign();
                trainingAssign.setServerId(serverDto.getId());
                trainingAssign.setTaskId(id);
                trainingAssignService.save(trainingAssign);
            } else {
                task.setStatus(TrainingInfoDto.TASK_STATUS_ERROR);
            }
            return updateById(task) && tag;
        }else{
            throw new BDException("不存在训练任务");
        }
    }

    // ===================新增关闭数据预测任务====================//
    @Override
     public boolean stopTask(Long id) {
        TrainingInfo task = getById(id);
        if (task != null) {
            // 判断任务执行状态,排队,执行中的任务才可以停止
            String status = task.getStatus();
            if (TrainingInfoDto.TASK_STATUS_NOTSTARTED.equals(status)
                    || TrainingInfoDto.TASK_STATUS_COMPLETE.equals(status)
                    || TrainingInfoDto.TASK_STATUS_STOP.equals(status)
                    || TrainingInfoDto.TASK_STATUS_ERROR.equals(status)) {
                logger.warn("id为[{}]的训练任务状态为[{}],不需要停止", id, status);
                return false;
            }

            // 根据训练任务id找出任务所在的训练服务器(训练任务分配表)
            // 判断该服务器是否在线
            // 判断该服务器是否在线
            QueryWrapper<TrainingAssign> wrapper = new QueryWrapper<>();
            wrapper.eq("task_id", id);
            TrainingAssign trainingAssign = trainingAssignService.getOne(wrapper);
            if (trainingAssign == null) {
                logger.warn("id为[{}]的训练任务没有找到训练服务器", id);
                return false;
            }
            TrainingServer trainingServer = serverService.getById(trainingAssign.getServerId());
            if (trainingServer == null
                    || TrainingServerDto.TRAINING_SERVER_STATUS_OFFLINE.equals(trainingServer.getStatus())) {
                logger.warn("id为[{}]的训练服务器不存在或已下线", trainingAssign.getServerId());
                return false;
            }
            // 向此训练服务器发送停止任务,调用算法端ip/stopTask接口
            boolean tag = serverHttpApi
                    .stopDataTypeTask(BeanCopyUtils.copyProperties(trainingServer, TrainingServerDto.class), id);
            // 修改任务状态为NotStarted
            if (tag) {
                if (TrainingInfoDto.TASK_STATUS_QUEUING.equals(status)) {
                    task.setStatus(TrainingInfoDto.TASK_STATUS_NOTSTARTED);
                } else {
                    task.setStatus(TrainingInfoDto.TASK_STATUS_STOP);
                }
                // 清除任务与服务器的关联关系
                trainingAssignService.remove(wrapper);
            } else {
                task.setStatus(TrainingInfoDto.TASK_STATUS_ERROR);
            }
            return updateById(task) && tag;
        }
        logger.warn("找不到id为[{}]的训练任务", id);
        return false;
    }


}
